package assignment1;

import java.net.Socket;
import java.net.ServerSocket;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

public class Server {
	private final int port;
	private ServerSocket socket;
	private boolean isClosed = false;
	
	public Server(int port){
		this.port = port;
		try{
			this.socket = new ServerSocket(this.port);
			System.out.println("Server bound to port: " + this.port);
		}catch(IOException iex){
			System.out.println("Failed to initialize server socket on port: " + this.port);
			System.exit(-1);
		}
	}
	
	/**
	 * Start the server and wait for clients to connect.
	 * When connected start a thread handling the client interaction.
	 * @return True if the server started successfully, false if not.
	 */
	public boolean start(){
		new Thread(new Runnable(){
			@Override
			public void run() {
				while(!isClosed){
					try{
						System.out.println("Waiting for client to connect...");
						Socket client = socket.accept();
						System.out.println("Client connected!");
						new Thread(new ClientHandler(client)).start();
					}catch(IOException iex){
						if(!isClosed){
							System.out.println("Cant listen for clients");
							System.exit(-1);
						}
					}
				}
			}
		}).start();
		return true;
	}
	
	public void stop(){
		try {
			isClosed = true;
			socket.close();
		} catch (IOException e) {
			isClosed = false;
			System.out.println("Failed to stop server!");
		}
	}
	
	private class ClientHandler implements Runnable{
		private Socket client;
		private ObjectOutputStream oos;
		private ObjectInputStream ois;
		private boolean connected;
		private boolean upperCase = true;
		
		public ClientHandler(Socket client){
			this.client = client;
			try {
				ois = new ObjectInputStream(client.getInputStream());
			} catch(IOException iex) {
				System.out.println("Failed to get client input stream.");
			}
			try {
				oos = new ObjectOutputStream(client.getOutputStream());
			} catch(IOException iex) {
				System.out.println("Failed to get client output stream.");
			}
			connected = true;
		}
		
		/**
		 * Read object send by client.
		 * @return The object read.
		 */
		public Object readObject() {
			try {
				return ois.readObject();
			} catch(Exception ex) {
				close();
			}
			return null;
		}
		
		/**
		 * Write an object to the client.
		 * @param obj The object to write.
		 */
		public void writeObject(Object obj){
			try {
				this.oos.writeObject(obj);
				this.oos.flush();
			} catch (Exception ex){
				close();
			}
		}
		
		/**
		 * Close the connection to the client.
		 */
		private void close(){
			connected = false;
			try {
				this.client.close();
			} catch (Exception e) {}
		}
		
		@Override
		public void run() {
			if(ois == null || oos == null) return;
			
			while(client != null && connected) {
				Object readObj = readObject();
				if(readObj != null) {
					if(readObj instanceof String) {
						String readStr = (String) readObj;
						System.out.println("Server received string: " + readStr);
						writeObject((this.upperCase ? readStr.toUpperCase() : readStr.toLowerCase()));
					}else if(readObj instanceof Integer) {
						this.upperCase = ((Integer)readObj > 0);
					}
				}
			}
			
			System.out.println("Client disconnected!");
		}
	}
	
	public static void main(String[] args) {
		int port = 34734;
		Server s = new Server(port);
		
		s.start();
	}
}
